﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UCodeFirstManagerBase.cs" company="CodePlex">
//   The MIT License
//   Copyright (c) 2013 Julien Blin
//   
//   Permission is hereby granted, free of charge, to any person obtaining a copy
//   of this software and associated documentation files (the "Software"), to deal
//   in the Software without restriction, including without limitation the rights
//   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//   copies of the Software, and to permit persons to whom the Software is
//   furnished to do so, subject to the following conditions:
//   
//   The above copyright notice and this permission notice shall be included in
//   all copies or substantial portions of the Software.
//   
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//   THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace UCodeFirst
{
    using System;
    using System.Globalization;
    using System.IO;
    using System.Web.Hosting;
    using System.Web.WebPages;

    using ServiceStack.Text;

    using Umbraco.Core;
    using Umbraco.Core.Logging;

    /// <summary>
    /// A UCodeFirst manager that will participate in the synchronization process.
    /// </summary>
    public abstract class UCodeFirstManagerBase
    {
        /// <summary>
        /// The umbraco application file path. (i.e. HostingEnvironment.MapPath("~")).
        /// </summary>
        protected readonly Lazy<string> UmbracoPath = new Lazy<string>(() => HostingEnvironment.MapPath("~"));

        /// <summary>
        /// Gets the priority with which this manager will run.
        /// A lower value means more priority (run before).
        /// If not overridden, defaults to <see cref="ManagerPriorities.DefaultPrority"/>.
        /// </summary>
        public virtual int Priority
        {
            get
            {
                return ManagerPriorities.DefaultPrority;
            }
        }

        /// <summary>
        /// Gets the <see cref="UCodeFirstApplicationEventHandler"/>.
        /// </summary>
        public UCodeFirstApplicationEventHandler UCodeFirstApplicationEventHandler { get; internal set; }

        /// <summary>
        /// All the managers <see cref="BeforeSynchronization"/> methods will run at the start of <see cref="UCodeFirstApplicationEventHandler"/>,
        /// no matter whether synchronization happens or not.<br />
        /// They run in the order specified by the <see cref="Priority"/>.
        /// </summary>
        /// <param name="httpApplication">
        /// The <see cref="UmbracoApplicationBase"/>.
        /// </param>
        /// <param name="applicationContext">
        /// The <see cref="ApplicationContext"/>.
        /// </param>
        public virtual void BeforeSynchronization(UmbracoApplicationBase httpApplication, ApplicationContext applicationContext)
        {
        }

        /// <summary>
        /// All the managers <see cref="Synchronize"/> methods will run after all the managers <see cref="BeforeSynchronization"/> method have run,
        /// <b>only</b> if the synchronization happens.<br />
        /// They run in the order specified by the <see cref="Priority"/>.
        /// </summary>
        /// <param name="httpApplication">
        ///     The <see cref="UmbracoApplicationBase"/>.
        /// </param>
        /// <param name="applicationContext">
        ///     The <see cref="ApplicationContext"/>.
        /// </param>
        public virtual void Synchronize(UmbracoApplicationBase httpApplication, ApplicationContext applicationContext)
        {
        }

        /// <summary>
        /// All the managers <see cref="AfterSynchronization"/> methods will run after all the managers <see cref="Synchronize"/> method have run,
        /// no matter whether synchronization happens or not.<br />
        /// They run in the inverse order specified by the <see cref="Priority"/>.
        /// </summary>
        /// <param name="httpApplication">
        ///     The <see cref="UmbracoApplicationBase"/>.
        /// </param>
        /// <param name="applicationContext">
        ///     The <see cref="ApplicationContext"/>.
        /// </param>
        /// <param name="synchronizationHappened">
        /// True if the synchronization happened.
        /// </param>
        public virtual void AfterSynchronization(UmbracoApplicationBase httpApplication, ApplicationContext applicationContext, bool synchronizationHappened)
        {
        }

        /// <summary>
        /// Returns the base type (specified with <code>@inherits</code> directive) of a razor view.
        /// Result is cached using <see cref="UCodeFirstData"/> and a content hash
        /// because instantiated a razor view for the first time is expensive.
        /// </summary>
        /// <param name="file">
        /// The file.
        /// </param>
        /// <returns>
        /// The base type.
        /// </returns>
        protected Type GetRazorViewBaseTypeWithCache(string file)
        {
            var alias = Path.GetFileNameWithoutExtension(file);
            var contentHash = File.ReadAllText(file).GetHashCode().ToString(CultureInfo.InvariantCulture);
            Type result = null;

            var cachedTemplateTypeValue = UCodeFirstData.GetValue(this.GetType(), alias);
            if (!string.IsNullOrEmpty(cachedTemplateTypeValue) && (UCodeFirstConfig.SynchronizationMode != SynchronizationMode.Force))
            {
                var cachedTemplateTypeValueSplitted = cachedTemplateTypeValue.Split(';');
                if ((cachedTemplateTypeValueSplitted.Length >= 2) && cachedTemplateTypeValueSplitted[0].InvariantEquals(contentHash))
                {
                    try
                    {
                        result = cachedTemplateTypeValueSplitted[1].To<Type>();
                    }
                    catch (Exception ex)
                    {
                        LogHelper.WarnWithException(this.GetType(), Resources.InternalError, ex);
                        UCodeFirstData.DeleteValue(this.GetType(), alias);
                    }
                }
            }

            if (result == null)
            {
                result = WebPageBase.CreateInstanceFromVirtualPath(this.ReverseMapPath(file)).GetType().BaseType;
                UCodeFirstData.SetValue(
                        this.GetType(),
                        alias,
                        string.Format("{0};{1}", contentHash, result.SerializeToString()));
            }

            return result;
        }

        /// <summary>
        /// Performs the reverse operation of <see cref="HostingEnvironment.MapPath"/>.
        /// </summary>
        /// <param name="path">
        /// The file path.
        /// </param>
        /// <returns>
        /// The virtual path.
        /// </returns>
        protected string ReverseMapPath(string path)
        {
            var relativePath = path.Replace(this.UmbracoPath.Value, string.Empty).Replace("\\", "/");
            return relativePath.InvariantStartsWith("/") ? "~" + relativePath : "~/" + relativePath;
        }
    }
}
